#include "Application.h"

void
application_init (Application* obj)
{
        object_init (OBJECT (obj));
        object_set_destory_func (obj, application_destory);
        obj->monitor    = monitor_new ();
        obj->num2       = number_new ();
        obj->state      = APP_STATE_INIT;
        obj->event      = APP_EVENT_NONE;
        obj->operate[0] = BUTTON_TYPE_NONE;
        obj->operate[1] = BUTTON_TYPE_NONE;

        /*构造时率先推送一次数码管内容（当前为 0）*/
        n_buffer_format (N_BUFFER (obj->monitor));
        monitor_refresh_cache (obj->monitor, true);
        monitor_push (MONITOR (obj->monitor));
}

void
application_destruction (Application* obj)
{
        /*析构函数要释放掉动态分配的两个成员变量*/
        object_unref (obj->monitor);
        object_unref (obj->num2);
}

OBJECT_DEFINE_START (Application, application)

/**
 * @brief 根据按钮类型获取事件类型
 *
 * @param obj 本对象
 */
static void
application_catch_event (Application* obj)
{
        switch (obj->button) {
        case BUTTON_TYPE_NONE:
                obj->event = APP_EVENT_NONE;
                break;
        case BUTTON_TYPE_DOT:
                obj->event = APP_EVENT_DOT;
                break;
        case BUTTON_TYPE_CLS:
                obj->event = APP_EVENT_CLEAN;
                break;
        case BUTTON_TYPE_EQU:
                obj->event = APP_EVENT_EQU;
                break;
        case BUTTON_TYPE_DIV:
        case BUTTON_TYPE_MUL:
        case BUTTON_TYPE_SUB:
        case BUTTON_TYPE_ADD:
                obj->event = APP_EVENT_OP;
                break;
        case BUTTON_TYPE_LEFT:
                obj->event = APP_EVENT_LEFT;
                break;
        case BUTTON_TYPE_RIGHT:
                obj->event = APP_EVENT_RIGHT;
                break;
        case BUTTON_TYPE_QUIT:
                obj->event = APP_EVENT_QUIT;
                break;
        default:
                obj->event = APP_EVENT_NUM_BTN;
                break;
        }
}

/**
 * @brief 此方法将数码管缓冲强制设定为 'Error'。
 *
 * @param obj 本对象
 */
static void
application_recover (Application* obj)
{
        obj->monitor->show[7] = 0x0;
        obj->monitor->show[6] = 0x0;
        obj->monitor->show[5] = 0x0;
        obj->monitor->show[4] = 0x79;
        obj->monitor->show[3] = 0x50;
        obj->monitor->show[2] = 0x50;
        obj->monitor->show[1] = 0x5c;
        obj->monitor->show[0] = 0x50;
}

/**
 * @brief 改变 Application 对象的状态为 state，当前的
 * 状态为 APP_STATE_WAIT_RECOVER 时，仅有
 * APP_STATE_INIT 才能改变 Application 的状态（即 Error
 * 之后，必须清零才能重置计算器）
 *
 * @param obj 本对象
 * @param state 要转换到的目标状态
 */
static void
application_state_change (Application* obj, AppState state)
{
        if (obj->state != APP_STATE_WAIT_RECOVER)
                obj->state = state;
        else if (obj->state == APP_STATE_WAIT_RECOVER &&
                 state == APP_STATE_INIT)
                obj->state = state;
}

/**
 * @brief 进行一次算术计算。计算时，operate[0] 用来判
 * 断需要进行何种计算，monitor 是数字 1，num2 为数字 2。
 *
 * @param obj 本对象
 */
static void
application_calculate (Application* obj)
{
        /*做算术运算时，若有一方数字为浮点数，则另一方无论
        是何类型，都要转换为浮点类型*/
        switch (obj->operate[0]) {
        case BUTTON_TYPE_ADD:
                if (NUMBER (obj->monitor)->type == N_TYPE_INTEGER &&
                    obj->num2->type == N_TYPE_INTEGER) {
                        NUMBER (obj->monitor)->number.l += obj->num2->number.l;
                } else {
                        number_change_type (NUMBER (obj->monitor),
                                            N_TYPE_DECIMAL);
                        number_change_type (obj->num2, N_TYPE_DECIMAL);
                        NUMBER (obj->monitor)->number.f += obj->num2->number.f;
                }
                break;
        case BUTTON_TYPE_SUB:
                if (NUMBER (obj->monitor)->type == N_TYPE_INTEGER &&
                    obj->num2->type == N_TYPE_INTEGER) {
                        NUMBER (obj->monitor)->number.l =
                                obj->num2->number.l -
                                NUMBER (obj->monitor)->number.l;
                } else {
                        number_change_type (NUMBER (obj->monitor),
                                            N_TYPE_DECIMAL);
                        number_change_type (obj->num2, N_TYPE_DECIMAL);
                        NUMBER (obj->monitor)->number.f =
                                obj->num2->number.f -
                                NUMBER (obj->monitor)->number.f;
                }
                break;
        case BUTTON_TYPE_MUL:
                if (NUMBER (obj->monitor)->type == N_TYPE_INTEGER &&
                    obj->num2->type == N_TYPE_INTEGER) {
                        NUMBER (obj->monitor)->number.l *= obj->num2->number.l;
                } else {
                        number_change_type (NUMBER (obj->monitor),
                                            N_TYPE_DECIMAL);
                        number_change_type (obj->num2, N_TYPE_DECIMAL);
                        NUMBER (obj->monitor)->number.f *= obj->num2->number.f;
                }
                break;
        case BUTTON_TYPE_DIV:
                /*做除法计算时，先把所有数字转换为浮点类型*/
                if (NUMBER (obj->monitor)->type == N_TYPE_INTEGER ||
                    obj->num2->type == N_TYPE_INTEGER) {
                        number_change_type (NUMBER (obj->monitor),
                                            N_TYPE_DECIMAL);
                        number_change_type (obj->num2, N_TYPE_DECIMAL);
                }
                if (NUMBER (obj->monitor)->number.f == 0.0) {
                        application_recover (obj);
                        application_state_change (obj, APP_STATE_WAIT_RECOVER);
                        break;
                }
                NUMBER (obj->monitor)->number.f =
                        obj->num2->number.f / NUMBER (obj->monitor)->number.f;
                break;
        default:
                break;
        }
}

/**
 * @brief 清空所有内容，用于按下清空按钮 C 时。
 *
 * @param obj 本对象
 */
static void
application_action_for_event_clean (Application* obj)
{
        number_init (NUMBER (obj->monitor));
        number_init (obj->num2);
        obj->operate[0] = BUTTON_TYPE_NONE;
        obj->operate[1] = BUTTON_TYPE_NONE;
}

void
application_run (Application* obj)
{
loop:
        obj->button = button_get_type ();
        if (obj->button == BUTTON_TYPE_NONE) {
                /*在实际的单片机中，这条 push 方法需要被执行*/
                // monitor_push (obj->monitor);
                goto loop;
        }

        /*按下按钮之后，获取按钮对应的事件*/
        application_catch_event (obj);

        /*左右移动事件不在状态机内，以下代码预先判断这两种事件*/
        if (obj->event == APP_EVENT_QUIT) {
                printf ("\n");
                return;
        } else if (obj->event == APP_EVENT_LEFT) {
                monitor_move_left (obj->monitor);
                monitor_refresh_cache (obj->monitor, false);
                monitor_push (obj->monitor);
                goto loop;
        } else if (obj->event == APP_EVENT_RIGHT) {
                monitor_move_right (obj->monitor);
                monitor_refresh_cache (obj->monitor, false);
                monitor_push (obj->monitor);
                goto loop;
        }

        /*Application 主状态机*/
        switch (obj->state) {
        case APP_STATE_INIT:
                if (obj->event == APP_EVENT_NUM_BTN) {
                        number_pull (NUMBER (obj->monitor), (uint8)obj->button);
                        application_state_change (obj, APP_STATE_WAIT_NUMBER);
                } else if (obj->event == APP_EVENT_DOT) {
                        number_change_type (NUMBER (obj->monitor),
                                            N_TYPE_DECIMAL);
                        application_state_change (obj, APP_STATE_WAIT_DECIMAL);
                } else if (obj->event == APP_EVENT_OP) {
                        obj->operate[0] = obj->operate[1];
                        obj->operate[1] = obj->button;
                        application_calculate (obj);
                        application_state_change (obj, APP_STATE_GET_OPERATOR);
                }
                break;
        case APP_STATE_WAIT_DECIMAL:
                if (obj->event == APP_EVENT_NUM_BTN) {
                        number_pull (NUMBER (obj->monitor), (uint8)obj->button);
                        application_state_change (obj, APP_STATE_WAIT_NUMBER);
                } else if (obj->event == APP_EVENT_OP ||
                           obj->event == APP_EVENT_EQU) {
                        application_recover (obj);
                        application_state_change (obj, APP_STATE_WAIT_RECOVER);
                } else if (obj->event == APP_EVENT_CLEAN) {
                        application_action_for_event_clean (obj);
                        application_state_change (obj, APP_STATE_INIT);
                }
                break;
        case APP_STATE_WAIT_NUMBER:
                if (obj->event == APP_EVENT_NUM_BTN) {
                        number_pull (NUMBER (obj->monitor), (uint8)obj->button);
                        application_state_change (obj, APP_STATE_WAIT_NUMBER);
                } else if (obj->event == APP_EVENT_DOT) {
                        number_change_type (NUMBER (obj->monitor),
                                            N_TYPE_DECIMAL);
                        application_state_change (obj, APP_STATE_WAIT_DECIMAL);
                } else if (obj->event == APP_EVENT_OP) {
                        obj->operate[0] = obj->operate[1];
                        obj->operate[1] = obj->button;
                        application_calculate (obj);
                        application_state_change (obj, APP_STATE_GET_OPERATOR);
                } else if (obj->event == APP_EVENT_EQU) {
                        obj->operate[0] = obj->operate[1];
                        obj->operate[1] = BUTTON_TYPE_NONE;
                        application_calculate (obj);
                        application_state_change (obj, APP_STATE_GET_EQU);
                } else if (obj->event == APP_EVENT_CLEAN) {
                        application_action_for_event_clean (obj);
                        application_state_change (obj, APP_STATE_INIT);
                }
                break;
        case APP_STATE_GET_OPERATOR:
                if (obj->event == APP_EVENT_NUM_BTN) {
                        number_copy_to (NUMBER (obj->monitor), obj->num2);
                        number_init (NUMBER (obj->monitor));
                        number_pull (NUMBER (obj->monitor), (uint8)obj->button);
                        application_state_change (obj, APP_STATE_WAIT_NUMBER);
                } else if (obj->event == APP_EVENT_DOT ||
                           obj->event == APP_EVENT_EQU) {
                        application_recover (obj);
                        application_state_change (obj, APP_STATE_WAIT_RECOVER);
                } else if (obj->event == APP_EVENT_OP) {
                        obj->operate[1] = obj->button;
                } else if (obj->event == APP_EVENT_CLEAN) {
                        application_action_for_event_clean (obj);
                        application_state_change (obj, APP_STATE_INIT);
                }
                break;
        case APP_STATE_WAIT_RECOVER:
                if (obj->event == APP_EVENT_CLEAN) {
                        application_action_for_event_clean (obj);
                        application_state_change (obj, APP_STATE_INIT);
                }
                break;
        case APP_STATE_GET_EQU:
                if (obj->event == APP_EVENT_NUM_BTN) {
                        number_init (NUMBER (obj->monitor));
                        number_init (obj->num2);
                        number_pull (NUMBER (obj->monitor), (uint8)obj->button);
                        application_state_change (obj, APP_STATE_WAIT_NUMBER);
                } else if (obj->event == APP_EVENT_DOT) {
                        number_init (NUMBER (obj->monitor));
                        number_init (obj->num2);
                        number_change_type (NUMBER (obj->monitor),
                                            N_TYPE_DECIMAL);
                        application_state_change (obj, APP_STATE_WAIT_DECIMAL);
                } else if (obj->event == APP_EVENT_OP) {
                        obj->operate[1] = obj->button;
                        application_state_change (obj, APP_STATE_GET_OPERATOR);
                } else if (obj->event == APP_EVENT_CLEAN) {
                        application_action_for_event_clean (obj);
                        application_state_change (obj, APP_STATE_INIT);
                }
                break;
        default:
                break;
        }

        /*处于 Error（即 APP_STATE_WAIT_RECOVER）时，停止刷新缓冲区，让其保持在
         * Error*/
        if (obj->state != APP_STATE_WAIT_RECOVER) {
                n_buffer_format (N_BUFFER (obj->monitor));
                monitor_refresh_cache (obj->monitor, true);
        }
        monitor_push (obj->monitor);

        goto loop;
}
